import { inherits as inherits$0 } from "util";
import Node from "../base/node";
import * as der from "../constants/der";
'use strict';
const inherits = { inherits: inherits$0 }.inherits;
function DEREncoder(entity) {
    this.enc = 'der';
    this.name = entity.name;
    this.entity = entity;
    // Construct base tree
    this.tree = new DERNode();
    this.tree._init(entity.body);
}
DEREncoder.prototype.encode = function encode(data, reporter) {
    return this.tree._encode(data, reporter).join();
};
// Tree methods
function DERNode(parent) {
    Node.call(this, 'der', parent);
}
inherits(DERNode, Node);
DERNode.prototype._encodeComposite = function encodeComposite(tag, primitive, cls, content) {
    const encodedTag = encodeTag(tag, primitive, cls, this.reporter);
    // Short form
    if (content.length < 0x80) {
        const header = Buffer.alloc(2);
        header[0] = encodedTag;
        header[1] = content.length;
        return this._createEncoderBuffer([header, content]);
    }
    // Long form
    // Count octets required to store length
    let lenOctets = 1;
    for (let i = content.length; i >= 0x100; i >>= 8)
        lenOctets++;
    const header = Buffer.alloc(1 + 1 + lenOctets);
    header[0] = encodedTag;
    header[1] = 0x80 | lenOctets;
    for (let i = 1 + lenOctets, j = content.length; j > 0; i--, j >>= 8)
        header[i] = j & 0xff;
    return this._createEncoderBuffer([header, content]);
};
DERNode.prototype._encodeStr = function encodeStr(str, tag) {
    if (tag === 'bitstr') {
        return this._createEncoderBuffer([str.unused | 0, str.data]);
    }
    else if (tag === 'bmpstr') {
        const buf = Buffer.alloc(str.length * 2);
        for (let i = 0; i < str.length; i++) {
            buf.writeUInt16BE(str.charCodeAt(i), i * 2);
        }
        return this._createEncoderBuffer(buf);
    }
    else if (tag === 'numstr') {
        if (!this._isNumstr(str)) {
            return this.reporter.error('Encoding of string type: numstr supports ' +
                'only digits and space');
        }
        return this._createEncoderBuffer(str);
    }
    else if (tag === 'printstr') {
        if (!this._isPrintstr(str)) {
            return this.reporter.error('Encoding of string type: printstr supports ' +
                'only latin upper and lower case letters, ' +
                'digits, space, apostrophe, left and rigth ' +
                'parenthesis, plus sign, comma, hyphen, ' +
                'dot, slash, colon, equal sign, ' +
                'question mark');
        }
        return this._createEncoderBuffer(str);
    }
    else if (/str$/.test(tag)) {
        return this._createEncoderBuffer(str);
    }
    else if (tag === 'objDesc') {
        return this._createEncoderBuffer(str);
    }
    else {
        return this.reporter.error('Encoding of string type: ' + tag +
            ' unsupported');
    }
};
DERNode.prototype._encodeObjid = function encodeObjid(id, values, relative) {
    if (typeof id === 'string') {
        if (!values)
            return this.reporter.error('string objid given, but no values map found');
        if (!values.hasOwnProperty(id))
            return this.reporter.error('objid not found in values map');
        id = values[id].split(/[\s.]+/g);
        for (let i = 0; i < id.length; i++)
            id[i] |= 0;
    }
    else if (Array.isArray(id)) {
        id = id.slice();
        for (let i = 0; i < id.length; i++)
            id[i] |= 0;
    }
    if (!Array.isArray(id)) {
        return this.reporter.error('objid() should be either array or string, ' +
            'got: ' + JSON.stringify(id));
    }
    if (!relative) {
        if (id[1] >= 40)
            return this.reporter.error('Second objid identifier OOB');
        id.splice(0, 2, id[0] * 40 + id[1]);
    }
    // Count number of octets
    let size = 0;
    for (let i = 0; i < id.length; i++) {
        let ident = id[i];
        for (size++; ident >= 0x80; ident >>= 7)
            size++;
    }
    const objid = Buffer.alloc(size);
    let offset = objid.length - 1;
    for (let i = id.length - 1; i >= 0; i--) {
        let ident = id[i];
        objid[offset--] = ident & 0x7f;
        while ((ident >>= 7) > 0)
            objid[offset--] = 0x80 | (ident & 0x7f);
    }
    return this._createEncoderBuffer(objid);
};
function two(num) {
    if (num < 10)
        return '0' + num;
    else
        return num;
}
DERNode.prototype._encodeTime = function encodeTime(time, tag) {
    let str;
    const date = new Date(time);
    if (tag === 'gentime') {
        str = [
            two(date.getUTCFullYear()),
            two(date.getUTCMonth() + 1),
            two(date.getUTCDate()),
            two(date.getUTCHours()),
            two(date.getUTCMinutes()),
            two(date.getUTCSeconds()),
            'Z'
        ].join('');
    }
    else if (tag === 'utctime') {
        str = [
            two(date.getUTCFullYear() % 100),
            two(date.getUTCMonth() + 1),
            two(date.getUTCDate()),
            two(date.getUTCHours()),
            two(date.getUTCMinutes()),
            two(date.getUTCSeconds()),
            'Z'
        ].join('');
    }
    else {
        this.reporter.error('Encoding ' + tag + ' time is not supported yet');
    }
    return this._encodeStr(str, 'octstr');
};
DERNode.prototype._encodeNull = function encodeNull() {
    return this._createEncoderBuffer('');
};
DERNode.prototype._encodeInt = function encodeInt(num, values) {
    if (typeof num === 'string') {
        if (!values)
            return this.reporter.error('String int or enum given, but no values map');
        if (!values.hasOwnProperty(num)) {
            return this.reporter.error('Values map doesn\'t contain: ' +
                JSON.stringify(num));
        }
        num = values[num];
    }
    // Bignum, assume big endian
    if (typeof num !== 'number' && !Buffer.isBuffer(num)) {
        const numArray = num.toArray();
        if (!num.sign && numArray[0] & 0x80) {
            numArray.unshift(0);
        }
        num = Buffer.from(numArray);
    }
    if (Buffer.isBuffer(num)) {
        let size = num.length;
        if (num.length === 0)
            size++;
        const out = Buffer.alloc(size);
        num.copy(out);
        if (num.length === 0)
            out[0] = 0;
        return this._createEncoderBuffer(out);
    }
    if (num < 0x80)
        return this._createEncoderBuffer(num);
    if (num < 0x100)
        return this._createEncoderBuffer([0, num]);
    let size = 1;
    for (let i = num; i >= 0x100; i >>= 8)
        size++;
    const out = new Array(size);
    for (let i = out.length - 1; i >= 0; i--) {
        out[i] = num & 0xff;
        num >>= 8;
    }
    if (out[0] & 0x80) {
        out.unshift(0);
    }
    return this._createEncoderBuffer(Buffer.from(out));
};
DERNode.prototype._encodeBool = function encodeBool(value) {
    return this._createEncoderBuffer(value ? 0xff : 0);
};
DERNode.prototype._use = function use(entity, obj) {
    if (typeof entity === 'function')
        entity = entity(obj);
    return entity._getEncoder('der').tree;
};
DERNode.prototype._skipDefault = function skipDefault(dataBuffer, reporter, parent) {
    const state = this._baseState;
    let i;
    if (state['default'] === null)
        return false;
    const data = dataBuffer.join();
    if (state.defaultBuffer === undefined)
        state.defaultBuffer = this._encodeValue(state['default'], reporter, parent).join();
    if (data.length !== state.defaultBuffer.length)
        return false;
    for (i = 0; i < data.length; i++)
        if (data[i] !== state.defaultBuffer[i])
            return false;
    return true;
};
// Utility methods
function encodeTag(tag, primitive, cls, reporter) {
    let res;
    if (tag === 'seqof')
        tag = 'seq';
    else if (tag === 'setof')
        tag = 'set';
    if (der.tagByName.hasOwnProperty(tag))
        res = der.tagByName[tag];
    else if (typeof tag === 'number' && (tag | 0) === tag)
        res = tag;
    else
        return reporter.error('Unknown tag: ' + tag);
    if (res >= 0x1f)
        return reporter.error('Multi-octet tag encoding unsupported');
    if (!primitive)
        res |= 0x20;
    res |= (der.tagClassByName[cls || 'universal'] << 6);
    return res;
}
export default DEREncoder;
